package org.framework.dao.plugin;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import com.hiwan.platform.util.StackTraceUtil;

/**
 * mapper.xml热加载，修改mapper.xml不需要重启tomcat,正式环境需要去掉
 */
public class XMLMapperLoader implements DisposableBean, InitializingBean {
	private Logger logger = LoggerFactory.getLogger(getClass());

	// private transient String basePackage = null;
	private HashMap<String, String> fileMapping = new HashMap<String, String>();
	private Scanner scanner = null;
	private ScheduledExecutorService service = null;
	private SqlSessionFactory sqlSessionFactory;
	private String mapperLocations;
	private int period;    //多久重新刷新加载一次mapper文件， 单位为秒

	public void afterPropertiesSet() throws Exception {
		try {
			service = Executors.newScheduledThreadPool(1);
			// 触发文件监听事件
			scanner = new Scanner();
			scanner.scan();

			service.scheduleAtFixedRate(new Task(), period, period, TimeUnit.SECONDS);

		} catch (Exception e) {
			logger.error(StackTraceUtil.getStackTrace(e));
		}

	}

	public String getMapperLocations() {
		return mapperLocations;
	}

	public void setMapperLocations(String mapperLocations) {
		this.mapperLocations = mapperLocations;
	}

	class Task implements Runnable {
		public void run() {
			try {
				if (scanner.isChanged()) {
					logger.info("*Mapper.xml has changed ,reloading .......");
					scanner.reloadXML();
					logger.info("*Mapper.xml change finished .......");
				}
			} catch (Exception e) {
				logger.error(StackTraceUtil.getStackTrace(e));
			}
		}

	}

	@SuppressWarnings({ "rawtypes" })
	class Scanner {
		private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

		public Resource[] getResource() throws IOException {
			Resource[] resources = resourcePatternResolver
					.getResources(mapperLocations);
			return resources;
		}

		public void reloadXML() throws Exception {
			Configuration configuration = sqlSessionFactory.getConfiguration();
			// 移除加载项
			removeConfig(configuration);
			// 重新扫描加载

			Resource[] resources = getResource();
			if (resources != null) {
				for (int i = 0; i < resources.length; i++) {
					if (resources[i] == null) {
						continue;
					}
					try {
						XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
								resources[i].getInputStream(), configuration,
								resources[i].toString(),
								configuration.getSqlFragments());
						xmlMapperBuilder.parse();
					} catch (Exception e) {
						throw new NestedIOException(
								"Failed to parse mapping resource: '"
										+ resources[i] + "'", e);
					} finally {
						ErrorContext.instance().reset();
					}
				}
			}

		}

		private void removeConfig(Configuration configuration) throws Exception {
			Class<?> classConfig = configuration.getClass();
			clearMap(classConfig, configuration, "mappedStatements");
			clearMap(classConfig, configuration, "caches");
			clearMap(classConfig, configuration, "resultMaps");
			clearMap(classConfig, configuration, "parameterMaps");
			clearMap(classConfig, configuration, "keyGenerators");
			clearMap(classConfig, configuration, "sqlFragments");

			clearSet(classConfig, configuration, "loadedResources");

		}

		private void clearMap(Class<?> classConfig,
				Configuration configuration, String fieldName) throws Exception {
			Field field = classConfig.getDeclaredField(fieldName);
			field.setAccessible(true);
			Map mapConfig = (Map) field.get(configuration);
			mapConfig.clear();
		}

		private void clearSet(Class<?> classConfig,
				Configuration configuration, String fieldName) throws Exception {
			Field field = classConfig.getDeclaredField(fieldName);
			field.setAccessible(true);
			Set setConfig = (Set) field.get(configuration);
			setConfig.clear();
		}

		public void scan() throws IOException {
			if (!fileMapping.isEmpty()) {
				return;
			}

			Resource[] resources = getResource();
			if (resources != null) {
				for (int i = 0; i < resources.length; i++) {
					String multi_key = getValue(resources[i]);
					fileMapping.put(resources[i].getFilename(), multi_key);
				}
			}
		}

		private String getValue(Resource resource) throws IOException {
			String contentLength = String.valueOf((resource.contentLength()));
			String lastModified = String.valueOf((resource.lastModified()));
			return new StringBuilder(contentLength).append(lastModified)
					.toString();
		}

		public boolean isChanged() throws IOException {
			boolean isChanged = false;
			Resource[] resources = getResource();
			if (resources != null) {
				for (int i = 0; i < resources.length; i++) {
					String name = resources[i].getFilename();
					String value = fileMapping.get(name);
					String multi_key = getValue(resources[i]);
					if (!multi_key.equals(value)) {
						isChanged = true;
						fileMapping.put(name, multi_key);
					}
				}
			}
			return isChanged;
		}
	}

	public void destroy() throws Exception {
		if (service != null) {
			service.shutdownNow();
		}
	}

	public SqlSessionFactory getSqlSessionFactory() {
		return sqlSessionFactory;
	}

	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
		this.sqlSessionFactory = sqlSessionFactory;
	}

	public int getPeriod() {
		return period;
	}

	public void setPeriod(int period) {
		this.period = period;
	}

}
